package com.google.code.mergeinformer.config;

import com.google.code.mergeinformer.util.AppUtils;
import net.endofinternet.dbupdater.DbUpdater;
import org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory;
import org.hibernate.cfg.ImprovedNamingStrategy;
import org.hibernate.dialect.MySQL5Dialect;
import org.hibernate.dialect.PostgreSQL82Dialect;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.aspectj.EnableSpringConfigured;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder;
import org.springframework.security.authentication.encoding.PasswordEncoder;
import org.springframework.security.authentication.encoding.ShaPasswordEncoder;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author Alexander Shergin
 */
@Configuration
@EnableSpringConfigured
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
@ComponentScan({
        "com.google.code.mergeinformer.service"
})
public class ServicesConfig {

    private static final int HASH_ITERATIONS = 1000;
    private static final int SHA_STRENGTH = 256;

    @Bean
    public DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        return new DefaultMethodSecurityExpressionHandler();
    }

    @Bean
    public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler() {
        return new DefaultWebSecurityExpressionHandler();
    }

    @Bean
    public DataSource dataSource() {
        final DataSource dataSource;
        try {
            dataSource = (DataSource) new InitialContext().lookup(AppUtils.getProperty("app.jndi.datasource.name"));
        } catch (NamingException e) {
            throw new RuntimeException(e);
        }

        Connection connection = null;
        try {
            connection = dataSource.getConnection();
            DbUpdater.checkAndUpdate(
                    connection,
                    "/database.xml",
                    AppUtils.getProperty("app.db.schema"),
                    AppUtils.getProperty("app.db.versionTable"));
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (connection != null && !connection.isClosed()) {
                    connection.close();
                }
            } catch (SQLException ignored) {
            }
        }

        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        final LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
        bean.setDataSource(dataSource());
        bean.setPackagesToScan("com.google.code.mergeinformer.model");

        final HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
        jpaVendorAdapter.setDatabase(Database.POSTGRESQL);
        jpaVendorAdapter.setDatabasePlatform(PostgreSQL82Dialect.class.getCanonicalName());
        if (AppUtils.isDev()) {
            jpaVendorAdapter.setShowSql(true);
        }
        jpaVendorAdapter.setGenerateDdl(false);
        bean.setJpaVendorAdapter(jpaVendorAdapter);
        bean.setJpaProperties(additionalProperties());

        return bean;
    }

    final Properties additionalProperties() {
        final Properties properties = new Properties();

        properties.setProperty("hibernate.ejb.naming_strategy", ImprovedNamingStrategy.class.getName());
        properties.setProperty("hibernate.dialect", MySQL5Dialect.class.getName());

        properties.setProperty("javax.persistence.sharedCache.mode", "ALL");
        properties.setProperty("hibernate.cache.region.factory_class", SingletonEhCacheRegionFactory.class.getCanonicalName());
        properties.setProperty("hibernate.cache.use_second_level_cache", "true");
        properties.setProperty("hibernate.cache.use_query_cache", "true");
        if (AppUtils.isDev()) {
            properties.setProperty("hibernate.generate_statistics", "true");
        }

        return properties;
    }

    @Bean
    public PlatformTransactionManager transactionManager() throws Exception {
        return new JpaTransactionManager(entityManagerFactory().getObject());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        final MessageDigestPasswordEncoder bean = new ShaPasswordEncoder(SHA_STRENGTH);
        bean.setIterations(HASH_ITERATIONS);
        return bean;
    }

    @Bean
    public SvnClientManagerFactory svnClientManagerFactory() {
        return new SvnClientManagerFactory();
    }

    @Bean
    public ExecutorService executorService() {
        return Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    }

    @Bean
    public TaskScheduler scheduler() {
        final ThreadPoolTaskScheduler bean = new ThreadPoolTaskScheduler();
        bean.setPoolSize(Runtime.getRuntime().availableProcessors());
        return bean;
    }

    @Bean
    public LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }
}
